/**
 * 证券代码组件，包含了ETF、债券、基金、非标、理财直融、全部场景
 * 不初始化下拉数据为空，根据查询条件，向后台搜索，获取callback结果
 * 若有初始化值，则按初始化值（code）向后端去查结果
 * 组件中对应的value值是tradingCode
 * 相关接口设计：http://wiki.htzq.htsc.com.cn/pages/viewpage.action?pageId=92421150
 * by zxy
 * create 2020.08.25
 * modify 2020.09.23
 * modify 2021.07.13 支持：1、默认展示100条；2、鼠标移动上去，tooltip提示；3、添加查询中和无数据提示。
 */
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'dva';
import { LoadingOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import { Input, Tooltip, message, Form } from 'antd'; // AutoComplete
import debounce from 'lodash/debounce';
import codeType from './enum';
import { FormItemLayout, NoLableLayout } from '../../form/formPub';
import { AutoCompleteC } from '../../form';

// 给原有的DataSource添加label属性，值为原来的title
function transformOptions(dataSource) {
  return dataSource?.map((item) => {
    item.label = item.title;
    return item;
  });
}

class SecuritiesCodeC extends React.PureComponent {
  onSearchStatus = false; // 标记当前是否是查询状态

  doSearch = false; // 执行查询标记（解决前后两次搜索条件一致，导致因this.props.dataSource和nextProps.dataSource相等而无法展示搜索结果的情况）

  static defaultProps = {
    title: '证券代码',
    initialValue: undefined,
    placeholder: '请输入代码或简称进行搜索',
    allowClear: true, // 允许清空
    helper: '',
    onChange: () => {},
    onRef: () => {}, // 本组件的引用
    dataIndex: 'tradingCode',
    loading: false,
    width: '100%',
    codeType: codeType.all, // 默认无类型
    queryItems: {}, // 默认无附加查询条件
    exchangeCode: undefined, // 默认不传交易所代码
    titleDisplay: true, // 默认展示title
    isInitFirstData: false, // 默认不加载第一条数据
    noQueryForInit: false, // 默认初始化需要先获取下拉100条数据，若有初始化值，需要调接口进行回填
    dsFromParent: undefined, // 默认无外部的初始化下拉数据
    labalWrap: false,
    isInput: false, // 是否支持将输入框内字符串作为参数执行查询
  };

  constructor(props) {
    super(props);
    this.state = {
      dataSource: [], // AutoComplete数据源，根据查询条件不同，从后台获取
      loading: false, // 当向后台请求数据时，loading为true
    };
    this.ref = React.createRef();
    // 绑定lodash的防抖函数，避免频繁搜索的情况
    this.handleSearch = debounce(this.handleSearch, 500);
  }

  setInitialValue = (init) => {
    this.setState({
      init,
    });
  };

  componentDidMount() {
    this.ref.current = true; // 首次输入字母或者数字时设置true
    // 父组件对本组件的引用
    this.props.onRef(this);
    const { noQueryForInit, initialValue } = this.props;
    this.setInitialValue(initialValue); // 同步初始化设置初始值
    // 若初始化需要查询，则调用接口，获取初始数据
    if (noQueryForInit == false) {
      this.getSecuritiesList('', undefined, false);
    } else {
      this.initData = this.props.dsFromParent;
      this.setState({
        dataSource: this.props.dsFromParent,
      });
    }

    // 如果有初始化值，且初始化需要查询，则根据初始化的code进行查询，将查询结果作为列表初始化数据源
    if (initialValue && noQueryForInit == false) {
      this.getSecuritiesList(initialValue, undefined, false, true, true);
    }
  }

  // 非初始化状态下更改下拉选项(供父组件调用)(value值为exchangeCode-tradingCode)
  setSelect = (value) => {
    // 设置form值
    const valueF = {};
    valueF[this.props.dataIndex] = value;
    this.props.form.setFieldsValue(valueF);

    // 根据code进行查询，将查询结果作为列表数据源
    if (value) {
      this.getSecuritiesList(value, undefined, true, true, true);
    }
    this.handleChange(value);
  };

  componentDidUpdate(prevProps) {
    // 处理异步初始化场景
    const init = prevProps.initialValue;
    const initN = this.props.initialValue;
    if (JSON.stringify(init) !== JSON.stringify(initN)) {
      this.setState({
        init: initN,
      });
      // 异步场景下，若初始化值有变更，且允许查询，则执行查询回填
      if (initN) {
        if (this.props.noQueryForInit == false) {
          this.getSecuritiesList(initN, this.props, true, true, true);
        }
      } else {
        this.setSelect(null);
      }
    }
    // 处理额外的查询条件变更时（比如sppi场景租户更换了），要清空组件原选择内容
    const queryItems = prevProps.queryItems ? JSON.stringify(prevProps.queryItems) : '';
    const queryItemsN = this.props.queryItems ? JSON.stringify(this.props.queryItems) : '';
    if (queryItems != queryItemsN) {
      this.setSelect(null);
      this.getSecuritiesList('', this.props);
    }
    // 处理从外部获取的数据是异步接入的情况
    if (this.props.noQueryForInit == true) {
      const dsFromParent = prevProps.dsFromParent ? JSON.stringify(prevProps.dsFromParent) : '';
      const dsFromParentN = this.props.dsFromParent ? JSON.stringify(this.props.dsFromParent) : '';
      if (dsFromParent != dsFromParentN) {
        this.initData = this.props.dsFromParent;
        this.setState({
          dataSource: this.props.dsFromParent,
        });
      }
    }
  }

  // 向后台请求接口（handleChange，标记是否需要抛出onChange事件  needSplit 是否需要截取-进行查询）
  getSecuritiesList = (value, props, handleChange = false, setTooltip = true, needSplit = false) => {
    // 查询之前，先清空原数据
    this.setState({
      dataSource: undefined,
      loading: true,
      tooltipInfo: '',
    });

    const PropsT = props ? props : this.props;
    // 查询参数
    const payload = {
      codeType: PropsT.codeType, // 证券类型
      ...PropsT.queryItems, // 其他查询条件（如交易市场等）
    };
    if (value) {
      // 有些场景会把code-exchangeCode作为查询条件，此时将二者拆开，value中只查询name和code
      if (value.indexOf('-') < 0 || !needSplit) {
        payload.value = value;
      } else {
        payload.exchangeCode = value.split('-')[0]; // 交易所代码
        payload.value = value.split('-')[1]; // code or 缩写
      }
    }
    // 当传了交易所信息作为搜索条件时，在接口入参上添加该值
    if (PropsT.exchangeCode != undefined) {
      payload.exchangeCode = PropsT.exchangeCode;
    }

    this.props.dispatch({
      type: 'securitiesCodeM/getSecuritiesList',
      payload,
      callback: (e) => {
        // 初始化默认加载100条数据，放在initData中管理
        if (value == '' && this.initData == undefined) {
          this.initData = e;
        }
        this.setState(
          {
            dataSource: e,
            loading: false,
          },
          () => {
            // 处理初始化时抛出onChange事件
            if (handleChange && e && e.length > 0) {
              this.props.onChange(e[0]);
            }

            // 若要求定位在第一条上
            if (this.props.isInitFirstData && e && e.length > 0) {
              // 设置form值
              const valueF = {};
              valueF[this.props.dataIndex] = e[0].value;
              this.props.form.setFieldsValue(valueF);

              // 抛出事件
              this.props.onChange(e[0]);
            }

            // 因初始化数据时，是代码自动选中的，并未触发handleSelect操作，顾此处也用代码添加上tooltip
            if (setTooltip && value && e && e.length > 0) {
              this.setState({
                tooltipInfo: e[0].title,
              });
            }
          },
        );
      },
    });
  };

  // 执行查询
  handleSearch = (value) => {
    // 为什么使用延时器？因为选词结束的时候search会比compositionend先一步触发，此时flag还未调整为true，故用setTimeout将其优先级滞后。
    setTimeout(() => {
      if (this.ref.current) {
        // 当搜索条件为空时，清空数据源
        if (!value) {
          this.setState({
            dataSource: this.initData,
          });
        } else {
          this.onSearchStatus = true;
          this.doSearch = true;
          this.getSecuritiesList(value, undefined, false, false);
        }
      }
    }, 0);
  };

  // 执行选中
  handleSelect = (value) => {
    const { isInput } = this.props;
    if (!isInput) {
      this.onSearchStatus = false;
      const findInDS = (this.state.dataSource || []).filter((item) => item.value === value);
      if (findInDS.length > 0) {
        // 对外抛出事件
        this.props.onChange(findInDS[0]);
        // 更改tooltip显示内容
        this.setState({
          tooltipInfo: findInDS[0].title,
        });
      } else {
        this.props.onChange(value);
      }
    }
  };

  // 选中 option，或 input 的 value 变化时，调用此函数
  // 这里主要处理allowClear设置为true时，点击clear按钮触发的change事件
  handleChange = (value) => {
    this.setState({ init: undefined });
    const that = this;
    if (!value) {
      this.getSecuritiesList('', undefined, false);
      // 触发选项切换事件
      that.props.onChange(null);
      // 更改tooltip显示内容
      that.setState({
        tooltipInfo: '',
      });
      return;
    }
    const { isInput } = that.props;
    if (!isInput) {
      return;
    }
    // 允许手动输入抛出选中 加一个防抖
    that.tradChangeTimer && clearTimeout(that.tradChangeTimer);
    that.tradChangeTimer = setTimeout(() => {
      that.onSearchStatus = false;
      const findInDS = (that.state.dataSource || []).filter((item) => item.value === value);
      if (findInDS.length > 0) {
        // 对外抛出事件
        that.props.onChange(findInDS[0]);
        // 更改tooltip显示内容
        that.setState({
          tooltipInfo: findInDS[0].title,
        });
      } else {
        that.props.onChange(value);
      }
    }, 300);
  };

  // 焦点离开事件
  onBlur = (e) => {
    const { isInput } = this.props;
    if (!isInput) {
      /* 对于查询了信息，却未选中查询结果中的信息而焦点离开了，采用的策略是：清空。 */
      if (this.onSearchStatus && e.target.value.trim() !== '') {
        message.info('未匹配，请重新输入查询条件。');
        // 清空输入内容
        const valueF = {};
        valueF[this.props.dataIndex] = null;
        this.props.form.setFieldsValue(valueF);
        // 触发选项切换事件
        this.props.onChange(null);
        // 更改tooltip显示内容
        this.setState({
          tooltipInfo: '',
        });
        this.getSecuritiesList('', undefined, false);
      }
    }
  };

  render() {
    const { labalWrap, styleRow, dataIndex, initialValue, placeholder, style, disabled, helper, allowClear, extraLayout, rules, width, title, titleDisplay, positionBody } =
      this.props;
    const { loading, init } = this.state;
    let layout = extraLayout ? extraLayout : FormItemLayout;
    if (titleDisplay === false) {
      layout = NoLableLayout;
    }
    return (
      <Tooltip placement='top' title={this.state.tooltipInfo}>
        <Form.Item
          colon={false}
          className={labalWrap ? 'labalWrap' : ''}
          style={style}
          {...layout}
          name={dataIndex}
          initialValue={this.state.init}
          rules={rules ? rules : []}
          label={
            helper ? (
              <span>
                {title}&nbsp;
                <Tooltip title={helper}>
                  <QuestionCircleOutlined />
                </Tooltip>
              </span>
            ) : (
              <span>{title}</span>
            )
          }
        >
          <AutoCompleteC
            initialValue={this.state.init}
            style={styleRow ? styleRow : { width }}
            options={transformOptions(this.state.dataSource)}
            onSearch={this.handleSearch}
            onSelect={this.handleSelect}
            onChange={this.handleChange}
            placeholder={placeholder}
            allowClear={allowClear}
            dropdownClassName={this.theme}
            disabled={disabled}
            getPopupContainer={(triggerNode) => (!positionBody ? triggerNode.parentNode : document.body)}
          >
            <Input
              onCompositionStart={() => {
                this.ref.current = false;
              }} // 处理“虚拟文本”
              onCompositionEnd={() => {
                this.ref.current = true;
              }} // 处理“虚拟文本”
              onBlur={this.onBlur}
              suffix={<LoadingOutlined style={{ fontSize: loading ? 12 : 0, color: '#999' }} />}
            />
          </AutoCompleteC>
        </Form.Item>
      </Tooltip>
    );
  }
}

// props默认值
SecuritiesCodeC.propsTypes = {
  form: PropTypes.object, // form对象
  title: PropTypes.string, // 显示名称
  dataIndex: PropTypes.string, // Form识别的Item ID
  initialValue: PropTypes.object, // 初始值（传code）
  helper: PropTypes.oneOfType([
    // 提示帮助信息
    PropTypes.string,
    PropTypes.element,
  ]),
  placeholder: PropTypes.string, // 占位符
  onChange: PropTypes.func, // 选项变更事件
  allowClear: PropTypes.boolean, // 设置允许清空输入内容
  dataSource: PropTypes.array, // 通过搜索条件，从后台获取的符合查询条件的数据源
  onRef: PropTypes.func, // 本组件的引用（可用来父组件调用内部方法）
  rules: PropTypes.array, // 校验规则
  extraLayout: PropTypes.object, // {labelCol:{},wrapperCol:{}}
  disabled: PropTypes.boolean, // 是否禁用
  style: PropTypes.object, // 样式
  codeType: PropTypes.string, // 证券代码类型
  exchangeCode: PropTypes.string, // 交易所代码
  queryItems: PropTypes.object, // 附加查询条件
  titleDisplay: PropTypes.boolean, // 是否展示title
  isInitFirstData: PropTypes.boolean, // 是否默认加载第一条数据，该值设为true时
  // 避免一个页面有很多证券代码组件，页面调很多次接口，特意增加了以下两个参数
  noQueryForInit: PropTypes.boolean, // 是否需要初始化调用接口，包含初始的100条数据和初始化回填
  dsFromParent: PropTypes.array, // 若noQueryForInit为true时，可以设置这个参数，下拉初始化默认用这个数据。
  isInput: PropTypes.boolean, // 是否将输入框内容作为值
  positionBody: PropTypes.bool, //  下拉框默认跟随，如果select父节点有滚动，下拉框被遮挡，则需要设置为true
};

export default connect(({ securitiesCodeM }) => ({ securitiesCodeM }))(SecuritiesCodeC);
